/*
 * Open LiveReplay Server - Live replay server for Supreme Commander
 * 
 * Copyright (C) 2009, 2009 Open LiveReplay Server Project
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details:
 * http://www.gnu.org/copyleft/lesser.html#TOC3
 */
package net.galacticwar.replayserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.management.ManagementFactory;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.registry.LocateRegistry;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

import org.apache.mina.integration.jmx.IoServiceManager;
import org.apache.mina.transport.socket.nio.SocketAcceptor;
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import com.sun.org.apache.xml.internal.serialize.OutputFormat;
import com.sun.org.apache.xml.internal.serialize.XMLSerializer;

/**
 * 
 * The main class of the Open Livereplay Server
 * Created on 16/01/2009
 * 
 * @author Yoann Meste (aka Mancer)
 */
public class ReplayServer {
	private static final Logger logger = Logger
			.getLogger("ReplayServer");
	
	private static int CHECK_ACTIVITY_DELAY = 24*60*60*1000; //1 day
	private static int CHECK_ACTIVITY_PERIOD = 24*60*60*1000; //1 day
	
	
	private final int PORT = 15000;
	private final int JMX_PORT = 6881;
	private final int EXTERNAL_REQUEST_PORT = 17001;
	
	
	private SocketAcceptor acceptor;
	
	private Map<String, LiveReplay> replays = new ConcurrentHashMap<String, LiveReplay>();
	

	
	
	
	public ReplayServer() throws IOException{
		acceptor = new SocketAcceptor();
		addJMXFunctions(acceptor);
        
		//init the replay filter
		ReplayFilter.getInstance();
		
        SocketAcceptorConfig cfg = new SocketAcceptorConfig();

        acceptor.bind( new InetSocketAddress(PORT),new ReplayServerHandler(replays), cfg);
        
        logger.log(Level.INFO, "Server listening port {0} for supcom requests", PORT);
        
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new CheckActivityTask(), CHECK_ACTIVITY_DELAY, CHECK_ACTIVITY_PERIOD);
        
        listenPort();
	}
	
	public static void main(String[] args) {
	    try {
	        // Create a file handler that write log record to a file
	        FileHandler handler = new FileHandler("LiveReplayServer.log", true);
	        handler.setFormatter(new SimpleFormatter());
	        // Add to the logger
	        logger.addHandler(handler);

	        logger.log(Level.INFO, "------- Live Replay Server starts -------");
			new ReplayServer();
		} catch (IOException e) {
			logger.log(Level.SEVERE, "Error" , e);
		}
	}
	
	/**
	 * Add basic JMX functionalities to the server
	 * @param acceptor
	 */
	private void addJMXFunctions(SocketAcceptor acceptor) {
		MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

		try {
			LocateRegistry.createRegistry(JMX_PORT);
			
	        JMXServiceURL url =
	            new JMXServiceURL("service:jmx:rmi://localhost"+
	            ":"+JMX_PORT+"/jndi/rmi://localhost:"+JMX_PORT+"/jmxrmi");

			// The environment map
			Map<String, String> env = new HashMap<String, String>();
			env.put("jmx.remote.x.password.file", "password.file");
			env.put("jmx.remote.x.access.file", "access.file");
			
			JMXConnectorServer cs =
                JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
			cs.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
              

		
		ObjectName name;
		try {
			name = new ObjectName("net.galacticwar.replayserver.ReplayServer:type=IoServiceManager");
			mbs.registerMBean( new IoServiceManager( acceptor ), name);
		
			name = new ObjectName("net.galacticwar.replayserver.ReplayServer:type=ServerManager");
			mbs.registerMBean(new ServerManager(replays), name);

		} catch (Exception e) {
			logger.log(Level.SEVERE, "Error" , e);
		}
		
		logger.log(Level.INFO, "JMX functions initializated on port {0}", JMX_PORT );

		

	}
	
	/**
	 * This class checks regularly if the replay are still in use
	 * If not, the replays is closed
	 * 
	 * @author Yoann Meste (aka Mancer)
	 *
	 */
	private class CheckActivityTask extends TimerTask{
		@Override
		public void run() {
			long currentTime = System.currentTimeMillis();
			for (LiveReplay replay : replays.values()) {
				if (replay.isInProgress() == false && 
						currentTime > replay.getLastActivity() + 24*60*60*1000) {
					// replay is not active since one day
					// So, we can remove it...
					
					replay.close();
					replays.remove(replay.getName());
				}
			}
			
		}
	}
	
	/**
	 * Export all active replays informations to an XML string
	 * 
	 * @param replays the {@code Map} containing all {@code LiverReplay}
	 * @return The XML formatted {@code String}
	 * @throws IOException
	 * @throws SAXException
	 */
	public static String toXML(Map<String, LiveReplay> replays) throws IOException, SAXException{

		Writer writer = new StringWriter();

		OutputFormat of = new OutputFormat("XML","UTF-8",true);
		of.setIndent(1);
		of.setIndenting(true);

		XMLSerializer serializer = new XMLSerializer(writer,of);

		ContentHandler hd = serializer.asContentHandler();
		hd.startDocument();

		hd.startElement("","","livereplay",new AttributesImpl());
		hd.startElement("","","replays", new AttributesImpl());

		long time = System.currentTimeMillis();

		for (LiveReplay liveReplay : replays.values()) {
			AttributesImpl replayAtts = new AttributesImpl();
			replayAtts.addAttribute("","","name","CDATA",liveReplay.getName());
			long fromStart = (time - liveReplay.getStartingDate().getTime())/1000l;
			replayAtts.addAttribute("","","time","CDATA",Long.toString(fromStart));
			replayAtts.addAttribute("","","inProgress","CDATA",Boolean.toString(liveReplay.isInProgress()));
			
			Replay replay = liveReplay.getReplay();
			
			if (replay != null) {
				replayAtts.addAttribute("","","map","CDATA",replay.getMap());
				hd.startElement("","","replay",replayAtts);
				
				hd.startElement("", "", "players" ,  new AttributesImpl());
				for (Player player : replay.getPlayers()) {
					AttributesImpl playerAtts = new AttributesImpl();
					playerAtts.addAttribute("","","name","CDATA",player.getName());
					playerAtts.addAttribute("","","faction","CDATA",player.getFaction());
					playerAtts.addAttribute("","","team","CDATA",player.getTeam().toString());
					playerAtts.addAttribute("","","color","CDATA",player.getColor().toString());
					
					hd.startElement("","","player",playerAtts);
					hd.endElement("","","player");
				}
				hd.endElement("","","players");

			}
			else{
				hd.startElement("","","replay",replayAtts);
			}

			hd.endElement("","","replay");
		}
		
		hd.endElement("", "","replays");
		hd.endElement("","","livereplay");
		hd.endDocument();

		writer.close();
		
		return writer.toString();

	}

	/**
	 * Open a socket for external status query requests  
	 */
	private void listenPort(){
		try {
			logger.log(Level.INFO, "Listening on port {0} for external requests", EXTERNAL_REQUEST_PORT);
			ServerSocket server_socket = new ServerSocket(EXTERNAL_REQUEST_PORT, 1);
			while (true) {

				try{

					Socket socket = server_socket.accept();
					logger.log(Level.INFO, "Session {0} connected on external request port", socket.getRemoteSocketAddress());

					BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
					Writer out = new OutputStreamWriter(socket.getOutputStream()); 
					
					out.write("Welcome on the livereplay server\r\n");
					out.flush();
					
					while(true) {
						String message = input.readLine();
						
						
						if (message==null){
							logger.log(Level.INFO, "Message null received");

							break;
						}

						logger.log(Level.INFO, "Character count : {0}", message.length());
						logger.log(Level.INFO, "Message received on external port {0}", message);

						
						if (message.equalsIgnoreCase("GET_REPLAYS")) {

							String xml;
							try {
								xml = toXML(replays);
								out.write(xml);
								out.write("\r\n");
								out.flush();
								logger.log(Level.INFO, "XML string sent");

							} catch (SAXException e) {
								logger.log(Level.WARNING, "Impossible to export replays data to XML", e);
							}
						}
						else {
							logger.log(Level.INFO, "Unknown command received {0}", message);
							break;
						}

					}
					out.write("Session closed.\r\n");
					out.flush();
					
					logger.log(Level.INFO, "Session {0} closed", socket.getRemoteSocketAddress());

					socket.close();

				}
				catch (IOException e) {
					logger.log(Level.WARNING, "Impossible to init the listening port", e);
				}

			}


		} catch (IOException e) {
			logger.log(Level.WARNING, "Exception during remote request", e);
		}

	}
	
}
